

class MLEParameterLearner:

    def __init__(self, network):
        self.network = network
        self.records = []

    # useSmoothing implements the Laplacian smoothing (i.e., "+1" smoothing)
    def learnParameters(self, records, useSmoothing = True):
        self.records = records
        self.useSmoothing = useSmoothing
        for i in range(self.network.getVariableCount()):
            self.setParameters(self.network.get(i))

    def getInstantiationCounts(self, variable):
        # Given the variable and its parents, find the counts associated with
        # all instantiations of it and its parents
        for record in self.records:
            j = variable.getParentIndex(record)
            value = record.get(variable.getIndex())
            k = variable.getValueIndex(value)
            variable.getParameters()[j][k] += 1

    def setParameters(self, variable):
        # first, make sure the parameters table is the correct size for this variable
        variable.updateParameterSize()
        self.getInstantiationCounts(variable)

        # for each parent intantiation, sum over the counts (taking into
        # account smoothing, if it is used). then, divide to find the 
        # final parameters.
        for pIndex in range(variable.getParametersSize()):
            total = 0
            for k in range(variable.getArity()):
                # this takes care of all of the smoothing
                if self.useSmoothing:
                    smoothedValue = variable.getParameter(pIndex, k) + 1
                    variable.setParameter(pIndex, k, smoothedValue)

                total += variable.getParameter(pIndex, k)

            for k in range(variable.getArity()):
                # no need to consider the smoothing here
                # just normalize
                parameterValue = variable.getParameter(pIndex, k) / total
                variable.setParameter(pIndex, k, parameterValue)
